/**
 * 2025/01/01 - 2025/12/31
 */
package com.snail.starter.iam.authentication.local.token.memory;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.snail.starter.utils.spring.SpringBeanContext;
import com.snail.starter.commons.threadlocal.UserContext;
import com.snail.starter.iam.authentication.jwt.JwtService;
import com.snail.starter.iam.authentication.local.token.AbstractTokenService;
import lombok.extern.slf4j.Slf4j;

import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @author zgadmin
 * @date 2025/8/26 15:43
 */
@Slf4j
public class MemoryTokenService extends AbstractTokenService {

    private static final Cache<String, Set<String>> LOGIN_NAME_MAPPING_TOKEN_CACHE = Caffeine.newBuilder().initialCapacity(100).maximumSize(100000).expireAfterWrite(TOKEN_EXPIRE, TimeUnit.MINUTES).removalListener(((key, value, cause) -> {
        log.debug("session cache remove key:{}, value:{}, cause:{}", key, value, cause);
    })).recordStats().build();

    private static final Cache<String, String> TOKEN_MAPPING_TOKEN_ENCODE_CACHE = Caffeine.newBuilder().initialCapacity(100).maximumSize(100000).expireAfterWrite(TOKEN_EXPIRE, TimeUnit.MINUTES).removalListener(((key, value, cause) -> {
        log.debug("token cache remove key:{}, value:{}, cause:{}", key, value, cause);
        if (null != value) {
            JwtService jwtService = SpringBeanContext.getBean(JwtService.class);
            UserContext userContext = jwtService.decode(value.toString());
            Set<String> tokenSet = LOGIN_NAME_MAPPING_TOKEN_CACHE.get(userContext.getUserName(), k -> new HashSet<>());
            if (null == tokenSet || tokenSet.isEmpty()) {
                return;
            }
            synchronized (MemoryTokenService.class) {
                Set<String> currentTokenSet = LOGIN_NAME_MAPPING_TOKEN_CACHE.get(userContext.getUserName(), k -> new HashSet<>());
                if (null == currentTokenSet || currentTokenSet.isEmpty()) {
                    return;
                }
                if (null != key) {
                    currentTokenSet.remove(key.toString());
                }
                LOGIN_NAME_MAPPING_TOKEN_CACHE.put(userContext.getUserName(), currentTokenSet);
            }
        }
    })).recordStats().build();

    public boolean checkMaxSession(String loginName, int maxSession) {
        Set<String> tokenSet = LOGIN_NAME_MAPPING_TOKEN_CACHE.get(loginName, key -> new HashSet<>());
        if (null == tokenSet || tokenSet.isEmpty()) {
            return true;
        }
        synchronized (MemoryTokenService.class) {
            return Objects.requireNonNull(LOGIN_NAME_MAPPING_TOKEN_CACHE.get(loginName, key -> new HashSet<>())).size() < maxSession;
        }
    }

    public void incrementSession(String loginName, String token) {
        synchronized (MemoryTokenService.class) {
            Set<String> tokenSet = LOGIN_NAME_MAPPING_TOKEN_CACHE.get(loginName, key -> new HashSet<>());
            if (null == tokenSet) {
                tokenSet = new HashSet<>();
            }
            tokenSet.add(token);
            LOGIN_NAME_MAPPING_TOKEN_CACHE.put(loginName, tokenSet);
        }
    }

    public void decrementSession(String loginName, String token) {
        Set<String> tokenSet = LOGIN_NAME_MAPPING_TOKEN_CACHE.get(loginName, key -> new HashSet<>());
        if (null == tokenSet || tokenSet.isEmpty()) {
            return;
        }
        synchronized (MemoryTokenService.class) {
            Set<String> currentTokenSet = LOGIN_NAME_MAPPING_TOKEN_CACHE.get(loginName, key -> new HashSet<>());
            if (null == currentTokenSet) {
                currentTokenSet = new HashSet<>();
            }
            currentTokenSet.remove(token);
            LOGIN_NAME_MAPPING_TOKEN_CACHE.put(loginName, currentTokenSet);
        }
    }

    @Override
    public void put(String token, String tokenEncode) {
        TOKEN_MAPPING_TOKEN_ENCODE_CACHE.put(token, tokenEncode);
    }

    @Override
    public boolean destroy(String token) {
        TOKEN_MAPPING_TOKEN_ENCODE_CACHE.invalidate(token);
        return false;
    }

    @Override
    public UserContext getUserContext(String token) {
        String tokenEncode = TOKEN_MAPPING_TOKEN_ENCODE_CACHE.getIfPresent(token);
        if (null == tokenEncode || "".equals(tokenEncode)) {
            return null;
        }
        UserContext userContext = jwtService.decode(tokenEncode);
        if (null == userContext) {
            return null;
        }
        if (!Objects.requireNonNull(LOGIN_NAME_MAPPING_TOKEN_CACHE.getIfPresent(userContext.getUserName())).contains(token)) {
            TOKEN_MAPPING_TOKEN_ENCODE_CACHE.invalidate(token);
            log.warn("login cache not exists, need invalidate token cache, userName:{}, token:{}", userContext.getUserName(), token);
            return null;
        }
        return userContext;
    }

    @Override
    public void delete(String loginName, String token) {
        TOKEN_MAPPING_TOKEN_ENCODE_CACHE.invalidate(token);
        decrementSession(loginName, token);
    }

    @Override
    public void deleteAll(String loginName) {
        Set<String> tokenSets = LOGIN_NAME_MAPPING_TOKEN_CACHE.getIfPresent(loginName);
        if (null == tokenSets || tokenSets.isEmpty()) {
            return;
        }
        TOKEN_MAPPING_TOKEN_ENCODE_CACHE.invalidateAll(tokenSets);
        LOGIN_NAME_MAPPING_TOKEN_CACHE.invalidate(loginName);
    }

}